package caches

import (
	"context"
	"fmt"
	"github.com/go-redis/redis/v8"
	"reflect"
	"sync"
	"time"
)

type CacheValueGetter func(ctx context.Context) (interface{}, error)

// 两级缓存，有以下功能
// 、支持两级(本地和redis)缓存
// 、提供主动清理本地缓存的功能
// 、提供主动清理两级缓存的功能
// 、基于redis通知机制，自动清理过期的本地缓存
type L2Cache struct {
	Cacher      *Cacher
	CacheName   string
	SystemName  string
	localCache  interface{}
	expireAt    *time.Time
	expire      time.Duration
	mutex       *sync.Mutex
	cacheKey    string
	valueGetter CacheValueGetter
}

func NewL2Cache(SystemName string, CacheName string, Getter CacheValueGetter, expire time.Duration, redisClient redis.UniversalClient) *L2Cache {
	return &L2Cache{
		Cacher:      NewCacher("cache:"+SystemName, redisClient),
		CacheName:   CacheName,
		SystemName:  SystemName,
		valueGetter: Getter,
		expire:      expire,
		cacheKey:    "v",
		mutex:       &sync.Mutex{},
	}
}

func (r *L2Cache) Get(ctx context.Context, dest interface{}) error {

	return r.Get(ctx, dest)
}

func (r *L2Cache) get(ctx context.Context, dest interface{}) error {
	// 判断本地缓存是否过期
	fmt.Println(r.expireAt)
	if r.expireAt != nil && r.expireAt.Before(time.Now()) {
		// 如果过期，则清理本地缓存
		fmt.Println("本地缓存过期")
		err := r.cleanLocalCache(ctx)
		if err != nil {
			return err
		}
	}
	// 判断本地缓存是否存在
	if r.localCache == nil {
		// 如果不存在，则从二级缓存加载
		err := r.loadFromL2(ctx, dest)
		if err != nil {
			return err
		}
	} else {
		r.setDest(dest)
	}
	return nil

}

func (r *L2Cache) setDest(dest interface{}) {
	rv := reflect.ValueOf(r.localCache)
	dv := reflect.ValueOf(dest)
	if rv.Kind() == reflect.Ptr && dv.Kind() == reflect.Ptr {
		dv.Elem().Set(rv.Elem())
	}
}

func (r *L2Cache) cleanLocalCache(ctx context.Context) error {
	r.localCache = nil
	r.expireAt = nil
	return nil
}

// 从二级缓存加载
func (r *L2Cache) loadFromL2(ctx context.Context, dest interface{}) error {
	// 加锁
	r.mutex.Lock()
	defer r.mutex.Unlock()
	// 再次查看一遍本地缓存
	if r.localCache != nil {
		return nil
	}
	// 从l2缓存获取数据
	isExist, err := r.Cacher.Get(ctx, r.cacheKey, dest)
	if err != nil {
		return err
	}
	if isExist {
		fmt.Println("从l2缓存获取数据")
		r.localCache = dest

		return nil
	}
	// 如果不存在，则加载数据，并设置到本缓存
	v, err := r.valueGetter(ctx)
	if err != nil {
		return err
	}
	err = r.set(ctx, v)
	if err != nil {
		return err
	}
	// 设置值
	r.setDest(dest)
	return nil
}

func (r *L2Cache) set(ctx context.Context, value interface{}) error {
	err := r.Cacher.Set(ctx, r.cacheKey, value, r.expire)
	if err != nil {
		return err
	}
	r.localCache = value
	e := time.Now().Add(r.expire)
	r.expireAt = &e
	return nil
}
